package org.skipper.oss.impl;

import com.aliyun.oss.*;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.auth.sts.AssumeRoleRequest;
import com.aliyuncs.auth.sts.AssumeRoleResponse;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.profile.DefaultProfile;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Setter;
import org.apache.commons.codec.binary.Base64;
import org.skipper.oss.OssClient;
import org.skipper.oss.domain.AliyunOssProperties;
import org.skipper.oss.result.PutSignatureForWXResult;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.InputStream;
import java.net.URL;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * Description:
 *
 * @author shoujian
 * Date: 2024-03-07
 * Time: 16:26
 */
@Setter
public class OssClientImpl implements OssClient {

    // STS临时访问密钥AccessKey ID和AccessKey Secret。
    private String accessKeyId;
    private String accessKeySecret;

    //yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1（杭州）为例，Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
    private String endpoint;

    private String bucketName;

    private String roleArn;

    @Override
    public PutObjectResult upload(String filePath, InputStream input) {

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        PutObjectResult result = null;
        try {

            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, filePath, input);

            // 上传字符串。
            result = ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return result;
    }

    @Override
    public PutObjectResult uploadWithUrl(String filePath, String url) {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            InputStream inputStream = new URL(url).openStream();
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, filePath, inputStream);
            // 创建PutObject请求。
            return ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return null;
    }

    @Override
    public URL generatePresignedUrl(String objectName, Date expiration) throws ClientException {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        URL url = null;
        try {
            // 生成以GET方法访问的签名URL，访客可以直接通过浏览器访问相关内容。
            url = ossClient.generatePresignedUrl(bucketName, objectName, expiration);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        return url;
    }

    @Override
    public URL snapshotFirstFrame(String videoUrl) {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        // 截帧，ss_0000为秒数，pw_100为宽度，ph_100为高度
        String style = "video/snapshot,t_0000,f_jpg,w_0,h_0";
        // 指定过期时间为10分钟。
        Date expiration = new Date(new Date().getTime() + 1000 * 60 * 10);
        GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(bucketName, videoUrl, HttpMethod.GET);
        req.setExpiration(expiration);
        req.setProcess(style);
        URL signedUrl = ossClient.generatePresignedUrl(req);
        // 关闭OSSClient。
        ossClient.shutdown();
        return signedUrl;
    }

    @Override
    public URL snapshotFrameByTime(Long time, String videoUrl) {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        // 截帧，ss_0000为毫秒数，pw_100为宽度，ph_100为高度
        String style = "video/snapshot,t_" + time + ",f_jpg,w_0,h_0";
        // 指定过期时间为10分钟。
        Date expiration = new Date(new Date().getTime() + 1000 * 60 * 10);
        GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(bucketName, videoUrl, HttpMethod.GET);
        req.setExpiration(expiration);
        req.setProcess(style);
        URL signedUrl = ossClient.generatePresignedUrl(req);
        // 关闭OSSClient。
        ossClient.shutdown();
        return signedUrl;
    }

    @Override
    public PutSignatureForWXResult getOssActualPutSignatureForWX() {

        //获取发送STS请求基础信息
        String regionId = "cn-hangzhou";    //发起STS请求所在的地域
        String roleSessionName = "oss_actual_put_signature";    //色会话名称，用来区分不同的令牌，可自定义
        Long durationSeconds = 3600L;   //临时访问凭证的有效时间

        //初始化客户端
        DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
        IAcsClient client = new DefaultAcsClient(profile);

        AssumeRoleRequest request = new AssumeRoleRequest();
        request.setRoleArn(roleArn);
        request.setRoleSessionName(roleSessionName);
        request.setDurationSeconds(durationSeconds);

        //定义STS临时访问凭证变量
        String STSaccessKeyId = null;
        String STSsecretAccessKey = null;
        String securityToken = null;

        try {
            AssumeRoleResponse response = client.getAcsResponse(request);

            //将请求返回的STS临时访问凭证赋值到自定义变量中
            STSaccessKeyId = response.getCredentials().getAccessKeyId();
            STSsecretAccessKey = response.getCredentials().getAccessKeySecret();
            securityToken = response.getCredentials().getSecurityToken();

        } catch (ClientException e) {
            e.printStackTrace();
        } catch (ServerException e) {
            throw new RuntimeException(e);
        } catch (com.aliyuncs.exceptions.ClientException e) {
            throw new RuntimeException(e);
        }

        //格式化请求日期
        long expiration = System.currentTimeMillis() / 1000;
        ZonedDateTime dtObj = ZonedDateTime.ofInstant(Instant.ofEpochSecond(expiration), ZoneId.of("UTC"));
        ZonedDateTime dtObjPlus3h = dtObj.plusHours(3);
        //请求时间
        DateTimeFormatter dtObj1Formatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'");
        String dtObj1 = dtObj.format(dtObj1Formatter);
        //请求日期
        DateTimeFormatter dtObj2Formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        String dtObj2 = dtObj.format(dtObj2Formatter);
        //请求过期时间
        DateTimeFormatter expirationTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        String expirationTime = dtObjPlus3h.format(expirationTimeFormatter);

        // 创建policy
        // 示例policy表单域只列举必填字段，如有其他需求可参考文档：https://help.aliyun.com/zh/oss/developer-reference/signature-version-4-recommend
        ObjectMapper mapper = new ObjectMapper();

        Map<String, Object> policy = new HashMap<>();
        policy.put("expiration", expirationTime);

        List<Object> conditions = new ArrayList<>();

        Map<String, String> bucketCondition = new HashMap<>();
        bucketCondition.put("bucket", bucketName);  //请将<bucketname>替换为您的实际Bucket名称
        conditions.add(bucketCondition);

        Map<String, String> signatureVersionCondition = new HashMap<>();
        signatureVersionCondition.put("x-oss-signature-version", "OSS4-HMAC-SHA256");
        conditions.add(signatureVersionCondition);

        Map<String, String> credentialCondition = new HashMap<>();
        credentialCondition.put("x-oss-credential", STSaccessKeyId + "/" + dtObj2 + "/cn-hangzhou/oss/aliyun_v4_request");     //请将<cn-hangzhou>替换为您的实际Bucket所处地域，例如北京地域为：cn-beijing
        conditions.add(credentialCondition);

        Map<String, String> token = new HashMap<>();
        token.put("x-oss-security-token", securityToken);
        conditions.add(token);

        Map<String, String> dateCondition = new HashMap<>();
        dateCondition.put("x-oss-date", dtObj1);
        conditions.add(dateCondition);

        policy.put("conditions", conditions);
        String jsonPolicy = null;
        try {
            jsonPolicy = mapper.writeValueAsString(policy);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        //构造待签名字符串（StringToSign）
        String stringToSign = new String(Base64.encodeBase64(jsonPolicy.getBytes()));

        //计算SigningKey
        byte[] dateKey = hmacsha256(("aliyun_v4" + STSsecretAccessKey).getBytes(), dtObj2);
        byte[] dateRegionKey = hmacsha256(dateKey, "cn-hangzhou");    //请将<cn-hangzhou>替换为您的实际Bucket所处地域，例如北京地域为：cn-beijing
        byte[] dateRegionServiceKey = hmacsha256(dateRegionKey, "oss");
        byte[] signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request");

        //计算Signature
        byte[] result = hmacsha256(signingKey, stringToSign);
        String signature = BinaryUtil.toHex(result);

        PutSignatureForWXResult putSignatureForWXResult = new PutSignatureForWXResult();
        putSignatureForWXResult.setSecurityToken(securityToken);
        putSignatureForWXResult.setSignature(signature);
        putSignatureForWXResult.setXOssDate(dtObj1);
        putSignatureForWXResult.setXOssCredential(STSaccessKeyId + "/" + dtObj2 + "/cn-hangzhou/oss/aliyun_v4_request");
        putSignatureForWXResult.setXOssSignatureVersion("OSS4-HMAC-SHA256");
        putSignatureForWXResult.setPolicy(stringToSign);

        return putSignatureForWXResult;
    }

    /**
     * 使用HMAC-SHA256算法计算给定密钥和数据的哈希值的静态方法
     *
     * @param key  钥匙
     * @param data 数据
     * @return {@link byte[] }
     */
    public static byte[] hmacsha256(byte[] key, String data) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(secretKeySpec);
            return mac.doFinal(data.getBytes());
        } catch (Exception e) {
            throw new RuntimeException("Failed to calculate HMAC-SHA256", e);
        }
    }


    /**
     * 创建oss客户端
     *
     * @param aliyunOssProperty alion oss属性
     * @return {@link OssClient}
     */
    public static OssClient create(AliyunOssProperties aliyunOssProperty) {
        OssClientImpl fileStorageServiceOss = new OssClientImpl();
        fileStorageServiceOss.setAccessKeyId(aliyunOssProperty.getAccessKeyId());
        fileStorageServiceOss.setAccessKeySecret(aliyunOssProperty.getAccessKeySecret());
        fileStorageServiceOss.setBucketName(aliyunOssProperty.getBucketName());
        fileStorageServiceOss.setEndpoint(aliyunOssProperty.getEndpoint());
        fileStorageServiceOss.setRoleArn(aliyunOssProperty.getRoleArn());
        return fileStorageServiceOss;
    }

}
